﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Numerics;
using System.Collections;

namespace evolutionary_algorithm
{
    abstract class Algorithm
    {
        protected double[] model; //Szukany wzorzec
        protected double[,] parents; //Tablica osobnikow
        protected double[,] children; //Tablica osobnikow wygenerowanych z parents
        protected int individuals; //Ilość osobników
        protected int genes; //Liczba genów dla każdego osobnika
        protected Random r = new Random(); //Inicjalizacja zegara losującego
        protected double[] fitnessParents; //Podobieństwo do wzorca rodzice
        protected double[] toDraw; //Tablica do losowania konkretnego osobnika
        public BigInteger counter = 0; //Licznik, ilosc zamian
        public bool doMutation = false; //Określamy czy mamy zastosować mutację
        protected int iGWOI = 0; // IGWOI (ID gene with one incompatibility) - ID genu, który ma tylko jedną niezgodność
        public ArrayList maxFitness = new ArrayList(); //Maksymalny fitness
        public ArrayList averageFitness = new ArrayList(); //Średni fitness
        

        protected void setFitnessParents (int who)
        {
            /*
             * Ustawianie podobieństwa w stosunku do wzorca dla rodzicow
             */
            int similarity = 0; // Okresla podobienstwo do wzorca
            for (int j = 0; j < genes; j++)
            {
                if (parents[who, j] == model[j])
                    similarity++;
            }
            fitnessParents[who] = 1 / ( ((double)genes+1) - (double)similarity);
        }


        protected double sumFitness()
        {
            /*
             * Suma podobienstw wszystkich osobników
             */
            double sumFitness = 0;

            for (int i = 0; i < individuals; i++)
            {
                sumFitness += fitnessParents[i]; 
            }

            return sumFitness;
        }

        protected void setDraw()
        {
            /*
             * Ustawia tablicę do losowania
             */
            toDraw = new double[individuals];
            double sumFitness = 0;

            for (int i = 0; i < individuals; i++)
            {
                sumFitness += fitnessParents[i];
                toDraw[i] = sumFitness;

            }
        }

        protected int drawIndividual()
        {
            /*
             * Losowanie konkretnego osobnika
             */
            double drawn = r.NextDouble() * sumFitness();

            if (drawn <= toDraw[0])
                return 0;
            for (int i = 1; i < individuals; i++)
            {
                if ((drawn > toDraw[i - 1]) && (drawn <= toDraw[i]))
                    return i;
            }
            return 666;
            /*
             * DOROBIĆ OBSŁUGĘ WYJĄTKÓW
             */
        }

        //protected void generate(int hMI, int hMP) //hMI - How Many Individuals (Ile osobników) hmp - How Many Division Points (Ile punktów podziału)
        public void generate(int hMI) //hMI - How Many Division Points and Individuals (Ile punktów podziału i osobników)
        {
            /*
             * Generowanie nowego pokolenia z wylosowanych osobników
             * Punktów nie może być więcej niż genów-1 oraz nie powinno byc mniej niz osobnikow, z ktorych ma utworzyc nowych
             */
            children = new double[individuals, genes]; //Deklarowanie tablicy osobników pochodnych
            int[] drawnIndividualsID = new int[hMI]; //Tablica z wylosowanymi osobnikami
            doMutation = false; //Ustawienie początkowej wartości robienia mutacji na false;
            int doMutationCounter = 0;

            int startPoint; //Punkt poczatkowy losowania
            int endPoint; //Punkt koncowy losowania
            int difference; //Roznica (bezwzgledna) losowania
            int genesNumber; //Ktory gen

            for (int i = 0; i < individuals; i++) //Główna pętla funkcji, w której tworzone są nowe osobniki a następnie przepisywane są one do tablicy głównych osobników
            {
                for (int ind = 0; ind < hMI; ind++)
                    drawnIndividualsID[ind] = drawIndividual(); //Losowanie do tablicy osobników

                genesNumber = 0;
                for (int dP = 0; (dP < hMI) && (genesNumber < genes); dP++)
                {
                    /*startPoint = r.Next(0, genes);
                    endPoint = r.Next(0, genes);*/
                    startPoint = genesNumber;
                    do
                        endPoint = r.Next(0, genes);
                    while (endPoint < startPoint);

                    if (startPoint > endPoint)
                    {
                        /*
                         * Zmiana kolejnosci
                         */
                        int copy = startPoint;
                        startPoint = endPoint;
                        endPoint = copy;
                    }
                    
                    difference = Math.Abs(startPoint-endPoint);

                    for (int a = 0; (a <= difference) && (genesNumber < genes); a++)
                    {
                        children[i, genesNumber] = parents[drawnIndividualsID[dP], (startPoint + a)];
                        genesNumber++;
                    }
                    /*
                     * Po to zerujemy dP, aby nowy osobnik wypelnione mial wszystkie geny.
                     * Jezeli genów jest 16 a bierzemy 3 osobników i np z pierwszego wylosujemy geny 1-2 z drugiego 3-8 i z trzeciego 9-11, mozemy zauwazyc, ze brakuje nam jeszcze 4
                     * do osiagniecia kompletu genow dla konkretnego osobnika.
                     * Wtedy od nowa bierzemy osobnika zrodlowego pierwszego i losujemy z niego, pozniej drugiego itd.
                     * Operaca zostanie zakonczona, jezeli osobnik ma juz wystarczajaca ilosc genow.
                     */
                    if (dP == hMI-1) 
                        dP = 0;
                } 
            }

            /*
             * Zamiana wylosowanych osobników z bieżącymi z tablicy parents
             */
            for (int i = 0; i < individuals; i++)
            {
                for (int j = 0; j < genes; j++)
                {
                    parents[i, j] = children[i, j];
                    //Console.Write(" " + parents[i, j]);
                }
                //Console.WriteLine();
                setFitnessParents(i);
            }
            setDraw();
            counter++;
            //Console.WriteLine();
            double toCheck = fitnessParents[0];
            for (int i = 0; i < individuals; i++)
            {
                if (toCheck == fitnessParents[i])
                    doMutationCounter++;
            }
            if (doMutationCounter == fitnessParents.Length)
                doMutation = true;

            statisticsOfFitness();
        }

        public int haveFound()
        {
            /*
             * Sprawdza czy znaleziono już osobnika zgodnego z wzorcem.
             * Możliwe stany:
             *  - 0: nie znaleziono,
             *  - 1: znaleziono,
             *  - 2: osobnik różni się co do wzorca tylko jednym genem, dlatego zostanie zastosowany inny algorytm
             */
            int sum;
            //Console.WriteLine();
            for (int i = 0; i < individuals; i++)
            {
                sum = 0;
                for (int j = 0; j < genes; j++)
                {
                    if (parents[i, j] == model[j])
                        sum++;
                   // Console.Write(parents[i, j]);
                }
                
                //Console.Write(" FIT: " + fitnessParents[i]);
                //Console.WriteLine();
                if (sum == genes)
                    return 1;
                else if ((sum - 1) == genes) //Przypadek, gdzie brakuje nam jednego genu, aby była zgodność w stosunku do wzorca
                {
                    return 2;
                }
            }
            return 0;
        }

        protected void statisticsOfFitness()
        {
            double max = fitnessParents[0];
            double average = 0;
            for (int i = 0; i < individuals; i++)
            {
                    if (max < fitnessParents[i])
                        max = fitnessParents[i];

                average += fitnessParents[i];
            }
            maxFitness.Add(max);
            averageFitness.Add((average/(double)individuals));
        }
    }
}
